package com.xunkids.bos.migration.component.log;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONValidator;
import com.xunkids.bos.migration.component.log.impl.KvLog;
import com.xunkids.bos.migration.component.log.impl.SysLog;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.layout.SerializedLayout;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;


/**
 * kt sys log
 *
 * @author xigexb
 * @version 1.0.0
 * @since 2022/10/30 11:24
 */
@Plugin(name = "bosDataMigrationSys", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
public final class BosDataMigrationSysLogAppender extends AbstractAppender {

    private final static Set<String> noLogClass = new HashSet<>();

    private final static AtomicLong ts = new AtomicLong(System.currentTimeMillis());

    private final static Long intervalMs = 20 * 1000L;

    static {
        String intervalStrMs = System.getProperty("com.xunkids.bos.migration.component.log.LogService.get.intervalMs");
        if (intervalStrMs != null && !intervalStrMs.isEmpty()) {
            try {
                long l = Long.parseLong(intervalStrMs);
                if (l > 60 * 1000) {
                    l = 10 * 1000;
                }
            } catch (Exception ignored) {

            }
        }
    }




    /**
     * Builds KtthingsSysLogAppender instances.
     *
     * @param <B> The type to build
     */
    public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
            implements org.apache.logging.log4j.core.util.Builder<BosDataMigrationSysLogAppender> {

        @PluginAttribute(value = "syncSend", defaultBoolean = true)
        private boolean syncSend;

        @Override
        public BosDataMigrationSysLogAppender build() {
            final Layout<? extends Serializable> layout = getLayout();
            if (layout == null) {
                AbstractLifeCycle.LOGGER.error("No layout provided for MigrationSysLogAppender");
                return null;
            }
            LogService logService = null;
            try {
                logService = SpringUtil.getBean(LogService.class);
            } catch (Exception e) {
                AbstractLifeCycle.LOGGER.info("MigrationSysLogAppender 还未获取到 logService");
            }
            return new BosDataMigrationSysLogAppender(getName(), layout, getFilter(), isIgnoreExceptions(), logService,
                    getPropertyArray());
        }

        public boolean isSyncSend() {
            return syncSend;
        }

        public B setSyncSend(final boolean syncSend) {
            this.syncSend = syncSend;
            return asBuilder();
        }
    }

    /**
     * Creates a builder for a KtthingsSysLogAppender.
     *
     * @return a builder for a KtthingsSysLogAppender.
     */
    @PluginBuilderFactory
    public static <B extends Builder<B>> B newBuilder() {
        return new Builder<B>().asBuilder();
    }

    private LogService logService;

    private BosDataMigrationSysLogAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
                                   final boolean ignoreExceptions, LogService logService, final Property[] properties) {
        super(name, filter, layout, ignoreExceptions, properties);
        this.logService = logService;
    }

    @Override
    public void append(final LogEvent event) {
        if (logService == null) {
            try {
                if (ts.get() != 0 && System.currentTimeMillis() - ts.get() > intervalMs) {
                    logService = SpringUtil.getBean(LogService.class);
                    ts.set(0);
                }
            } catch (Exception ignored) {
                AbstractLifeCycle.LOGGER.error("BosDataMigrationSysLogAppender 还未获取到 logService，忽略该日志");
            }
        }
        if (logService == null) {
            return;
        }
        try {
            if (noLogClass.contains(event.getLoggerFqcn())) {
                return;
            }
            tryAppend(event);
        } catch (final Exception e) {
            error("Unable to write to Log in appender [" + getName() + "]", event, e);
        }
    }

    @Override
    public void start() {
        super.start();
    }

    @Override
    public boolean stop(final long timeout, final TimeUnit timeUnit) {
        setStopping();
        boolean stopped = super.stop(timeout, timeUnit, false);
        setStopped();
        return stopped;
    }

    @Override
    public String toString() {
        return "bosDataMigrationSysLogAppender{" + "name=" + getName() + ", state=" + getState() + '}';
    }

    private void tryAppend(final LogEvent event) {
        final Layout<? extends Serializable> layout = getLayout();
        byte[] data;
        if (layout instanceof SerializedLayout) {
            final byte[] header = layout.getHeader();
            final byte[] body = layout.toByteArray(event);
            data = new byte[header.length + body.length];
            System.arraycopy(header, 0, data, 0, header.length);
            System.arraycopy(body, 0, data, header.length, body.length);
        } else {
            data = layout.toByteArray(event);
        }
        String msg = new String(data).replace("\r\n", "");
        KvLog kvLog = null;
        switch (event.getLevel().name().toLowerCase()) {
            case "fatal":
                kvLog = SysLog.fatal(msg);
                break;
            case "error":
                kvLog = SysLog.error(msg);
                break;
            case "warn":
                kvLog = SysLog.warn(msg);
                break;
            case "debug":
                kvLog = SysLog.debug(msg);
                break;
            case "trace":
                kvLog = SysLog.trac(msg);
                break;
            case "all":
                kvLog = SysLog.all(msg);
                break;
            default:
                kvLog = SysLog.info(msg);
                break;
        }
        kvLog.label("logImpl", "log4j2");
        kvLog.label("logName", event.getLoggerName());
        kvLog.put("threadName", event.getThreadName());
        kvLog.put("timeMillis", String.valueOf(event.getTimeMillis()));
        kvLog.put("loggerFqcn", event.getLoggerFqcn());
        if (event.getMarker() != null) {
            kvLog.put("marker", event.getMarker().getName());
        }
        String message = event.getMessage().getFormattedMessage();
        kvLog.put("formatMessage", message);
        if ((message.startsWith("{") || message.startsWith("[")) && (message.endsWith("}") || message.endsWith("]"))) {
            try {
                JSONValidator jsonValidator = JSONValidator.from(message);
                if (jsonValidator.validate()) {
                    JSONObject parseObject = JSONObject.parseObject(message);
                    KvLog finalKvLog = kvLog;
                    parseObject.forEach((k, v) -> {
                        finalKvLog.put("message_" + k, String.valueOf(v));
                    });
                }
            } catch (Exception ignored) {
            }
        }
        Object[] parameters = event.getMessage().getParameters();
        if (parameters != null) {
            kvLog.put("messageParamNum", String.valueOf(parameters.length));
            int i = 0;
            for (Object parameter : parameters) {
                if (parameter != null) {
                    i = i + 1;
                    kvLog.put("param_" + i, parameter + "_");
                }
            }
        }
        if (event.getMessage().getThrowable() != null) {
            kvLog.put("messageThrowable", ExceptionUtil.getMessage(event.getMessage().getThrowable()));
        }
        if (event.getMessage().getThrowable() != null) {
            kvLog.put("throwable", ExceptionUtil.getMessage(event.getMessage().getThrowable()));
        }
        kvLog.put("nanoTime", String.valueOf(event.getNanoTime()));
        StackTraceElement eventSource = event.getSource();
        if (eventSource != null) {
            kvLog.put("method", eventSource.getMethodName());
            kvLog.put("className", eventSource.getClassName());
            kvLog.put("classFile", eventSource.getFileName());
            kvLog.put("lineNumber", String.valueOf(eventSource.getLineNumber()));
        }

        kvLog.put("threadId", String.valueOf(event.getThreadId()));
        kvLog.put("threadPriority", String.valueOf(event.getThreadPriority()));
        if (event.getThrown() != null) {
            kvLog.put("thrown", ExceptionUtil.getMessage(event.getThrown()));
        }
        kvLog.put("timeMillis", String.valueOf(event.getTimeMillis()));
        logService.save(kvLog);
    }
}
